Введение в анализ данных¶

Домашнее задание 5. Анализ вакансий.¶

Правила, прочитайте внимательно:

  • Выполненную работу нужно отправить телеграм-боту @miptstats_ds22_bot. Для начала работы с ботом каждый раз отправляйте /start. Работы, присланные иным способом, не принимаются.
  • Дедлайн см. в боте. После дедлайна работы не принимаются кроме случаев наличия уважительной причины.
  • Прислать нужно ноутбук в формате ipynb и полученные данные (подробности далее).
  • Если вы выполняете сложную часть, то кроме ipynb прислать нужно также ноутбук, сконвертированный в формате html, который можно получить как File -> Download as -> HTML. Внимательно проверьте, что plotly-графики в ней сохранились.
  • Следите за размером файлов. Бот не может принимать файлы весом более 20 Мб. Если файл получается больше, заранее разделите его на несколько.
  • Будьте внимательны при работе со сбором данных. Ответственность за корректность ваших действий лежит на вас. Не нагружайте сервера, делайте паузы между запросами. Как следствие, начинайте выполнять задание заранее. Если вас где-то забаннили и т.п., то это не является уважительной причиной продления дедлайна.
  • Выполнять задание необходимо полностью самостоятельно. При обнаружении списывания все участники списывания будут сдавать устный зачет.
  • Решения, размещенные на каких-либо интернет-ресурсах, не принимаются. Кроме того, публикация решения в открытом доступе может быть приравнена к предоставлении возможности списать.
  • Для выполнения задания используйте этот ноутбук в качестве основы, ничего не удаляя из него. Можно добавлять необходимое количество ячеек.
  • Комментарии к решению пишите в markdown-ячейках.
  • Выполнение задания (ход решения, выводы и пр.) должно быть осуществлено на русском языке.
  • Если код будет не понятен проверяющему, оценка может быть снижена.
  • Никакой код из данного задания при проверке запускаться не будет. Если код студента не выполнен, недописан и т.д., то он не оценивается.

Баллы за задание:

Легкая часть (достаточно на "хор"):

  • Задача 1 — 80 баллов

Сложная часть (необходимо на "отл"):

  • Задача 2 — 60 баллов
In [3]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set(style='whitegrid', font_scale=1.3, palette='Set2')
%matplotlib inline

import requests
from time import sleep
import json

Второй курс — самое время задуматься о будущей профессии и проанализировать существующие предложения. Дело тут даже не в том, чтобы найти интересную стажировку. В первую очередь сейчас стоит подумать о том, в какую сторону развиваться дальше. Например, если вы хотите работать в какой-либо конкретной профессии, то наверняка стоит развивать какие-то определенные навыки, и даже выбрать подходящую кафедру. Анализ существующих вакансий поможет определить, какие навыки вам нужны.

В данном задании вам нужно проанализировать вакансии на сайте hh.ru с использованием официального API.

a47371.buy12r.1ets.rs.ii.jpg


Справка по работе с API.¶

Внимание! При работе с API не забывайте делать паузы между запросами, чтобы не задудосить сервер HeadHunter. Если вас заблокируют, это не будет являться уважительной причиной переноса дедлайна.

Мы будем работать только с вакансиями. Для этого не требуется регистрироваться и получать токен. Ниже приведен краткий пример работы с API. Подробное описание работы с вакансиями, включая параметры запросов и формат ответа можно почитать в документации.

Например, мы хотим найти вакансии по запросу Data Scientist в Москве. Тогда первую страницу поиска из 10 вакансий на страницу мы можем получить с запроса к API:

In [3]:
URL = 'https://api.hh.ru/vacancies'

params = {
    'text': "Data Scientist",
    'area': 1,
    'page': 0,
    'per_page': 10
}

req = requests.get(URL, params)
data = json.loads(req.content.decode())

Если все прошло успешно, полученный словарь будет иметь следующие ключи

In [4]:
data.keys()
Out[4]:
dict_keys(['items', 'found', 'pages', 'per_page', 'page', 'clusters', 'arguments', 'alternate_url'])

Можем посмотреть на краткое описание первой вакансии

In [5]:
data['items'][0]
Out[5]:
{'id': '49423067',
 'premium': False,
 'name': 'Data Scientist',
 'department': None,
 'has_test': False,
 'response_letter_required': False,
 'area': {'id': '1', 'name': 'Москва', 'url': 'https://api.hh.ru/areas/1'},
 'salary': {'from': None, 'to': 320000, 'currency': 'RUR', 'gross': False},
 'type': {'id': 'open', 'name': 'Открытая'},
 'address': None,
 'response_url': None,
 'sort_point_distance': None,
 'published_at': '2022-03-11T12:37:57+0300',
 'created_at': '2022-03-11T12:37:57+0300',
 'archived': False,
 'apply_alternate_url': 'https://hh.ru/applicant/vacancy_response?vacancyId=49423067',
 'insider_interview': None,
 'url': 'https://api.hh.ru/vacancies/49423067?host=hh.ru',
 'alternate_url': 'https://hh.ru/vacancy/49423067',
 'relations': [],
 'employer': {'id': '2324020',
  'name': 'Точка',
  'url': 'https://api.hh.ru/employers/2324020',
  'alternate_url': 'https://hh.ru/employer/2324020',
  'logo_urls': {'240': 'https://hhcdn.ru/employer-logo/3414734.jpeg',
   '90': 'https://hhcdn.ru/employer-logo/3414733.jpeg',
   'original': 'https://hhcdn.ru/employer-logo-original/743443.jpg'},
  'vacancies_url': 'https://api.hh.ru/vacancies?employer_id=2324020',
  'trusted': True},
 'snippet': {'requirement': 'Есть опыт работы с методами машинного обучения и основными инструментами <highlighttext>data</highlighttext> science: Scikit-learn, XGBoost, CatBoost и т.п. ',
  'responsibility': 'Придумывать и проверять гипотезы, которые позволят построить новые скоринговые модели и улучшить текущие. В результате они должны определять благонадёжность компаний...'},
 'contacts': None,
 'schedule': {'id': 'fullDay', 'name': 'Полный день'},
 'working_days': [],
 'working_time_intervals': [],
 'working_time_modes': [],
 'accept_temporary': False}

Сколько всего найдено вакансий

In [6]:
data['found']
Out[6]:
318

Количество страниц в результатах поиска

In [7]:
data['pages']
Out[7]:
32

Из результатов можем сделать удобную таблицу, причем в дальнейшем можно оставить только те колонки, которые необходимы для анализа.

In [8]:
df = pd.json_normalize(data['items'])
df.head()
Out[8]:
id premium name department has_test response_letter_required address response_url sort_point_distance published_at ... address.metro.line_id address.metro.lat address.metro.lng address.metro_stations address.id employer.logo_urls salary address.metro department.id department.name
0 49423067 False Data Scientist NaN False False NaN None None 2022-03-11T12:37:57+0300 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 53692325 False Junior Data Scientist (олимпиадные задачи по а... NaN False False NaN None None 2022-03-11T14:28:53+0300 ... 97 55.749100 37.539500 [{'station_name': 'Деловой центр', 'line_name'... 7401404 NaN NaN NaN NaN NaN
2 53211742 False Junior Data scientist/ Младший риск-аналитик NaN False True NaN None None 2022-03-13T11:16:44+0300 ... 2 55.789704 37.558212 [{'station_name': 'Динамо', 'line_name': 'Замо... 1551673 NaN NaN NaN NaN NaN
3 53092406 False Data scientist NaN False False NaN None None 2022-03-12T17:01:36+0300 ... NaN NaN NaN [] 953346 NaN NaN NaN NaN NaN
4 50138376 False Data engineer/ Инженер по данным (удаленно) NaN False False NaN None None 2022-03-11T18:39:01+0300 ... NaN NaN NaN NaN NaN NaN NaN NaN bil-4934-it билайн: ИТ и Digital

5 rows × 64 columns

Для получения полного описания вакансии потребуется задать отдельный запрос, используя ее id.

In [9]:
vacancy = df['id'].iloc[0]
vacancy_url = f'https://api.hh.ru/vacancies/{vacancy}'

req = requests.get(vacancy_url)
vacancy_info = json.loads(req.content.decode())
In [10]:
vacancy_info
Out[10]:
{'id': '49423067',
 'premium': False,
 'billing_type': {'id': 'standard_plus', 'name': 'Стандарт плюс'},
 'relations': [],
 'name': 'Data Scientist',
 'insider_interview': None,
 'response_letter_required': False,
 'area': {'id': '1', 'name': 'Москва', 'url': 'https://api.hh.ru/areas/1'},
 'salary': {'from': None, 'to': 320000, 'currency': 'RUR', 'gross': False},
 'type': {'id': 'open', 'name': 'Открытая'},
 'address': None,
 'allow_messages': True,
 'site': {'id': 'hh', 'name': 'hh.ru'},
 'experience': {'id': 'between3And6', 'name': 'От 3 до 6 лет'},
 'schedule': {'id': 'fullDay', 'name': 'Полный день'},
 'employment': {'id': 'full', 'name': 'Полная занятость'},
 'department': None,
 'contacts': None,
 'description': '<p>Ищем специалиста по Data Science, чтобы строить и улучшать модели машинного обучения, которые помогут усовершенствовать наш продукт и внутренние процессы. Мы стремимся к тому, чтобы мы стали быстрее и удобнее для клиентов. И в этом нам нужна твоя помощь.</p> <p><strong>Что нужно делать</strong></p> <p>Придумывать и проверять гипотезы, которые позволят построить новые скоринговые модели и улучшить текущие. В результате они должны определять благонадёжность компаний по данным о бизнесе.</p> <p>Ты будешь доставать данные из баз и других источников, приводить их к нужному виду, анализировать и затем строить модели. Тебе предстоит планировать и ставить эксперименты, анализировать результаты и их статистическую значимость. Использовать feature engineering, model selection и fine tuning.</p> <p><strong>Ты подойдешь, если</strong></p> <ul> <li> <p>Знаешь Python, умеешь обрабатывать данные с помощью pandas, NumPy, Plotly.</p> </li> <li> <p>У тебя есть опыт работы с SQL.</p> </li> <li> <p>Есть опыт работы с методами машинного обучения и основными инструментами data science: Scikit-learn, XGBoost, CatBoost и т.п.</p> </li> <li> <p>Разбираешься в математической статистике, теории вероятностей и знаешь, как проводить A/B тестирования.</p> </li> </ul> <p><strong>Будет плюсом, если</strong></p> <ul> <li> <p>Ты понимаешь принципы работы нейросетей и уже имеешь опыт с одним или несколькими фреймворками для глубокого обучения: TensorFlow или PyTorch.</p> </li> <li> <p>Умеешь вводить модели машинного обучения в работу production.</p> </li> </ul> <p><strong>Что будет твоим в Точке</strong></p> <ul> <li> <p>Зарплата от 100 000 ₽. Расскажи на собеседовании, какая зарплата будет комфортной для тебя. А мы определим твой грейд по хард и софт скиллам и назовём точную сумму.</p> </li> <li> <p>Официальное трудоустройство и бесплатная страховка здоровья даже за границей.</p> </li> <li> <p>Удобный график: гибкое начало и окончание рабочего дня.</p> </li> <li> <p>Современный и уютный офис со спортзалом и библиотекой, кофе-поинтами, кухнями и зонами отдыха.</p> </li> <li> <p>Гибкие методы управления: мы используем Agile и SCRUM, чтобы каждый день создавать новое и быстро реагировать на изменения.</p> </li> <li> <p>Бесплатное корпоративное обучение: мы ездим на IT-конференции, собираемся на митапы, раздаем призы на хакатонах и проходим курсы за счёт компании.</p> </li> <li> <p>Возможность принимать решения без тысячи согласований и предлагать идеи: от простых до самых амбициозных.</p> </li> </ul> <p>И всё это в комфортных условиях без бюрократии, дресс-кода и начальников.</p>',
 'branded_description': '\n<style>\n.tmpl_hh_wrapper p,\n.tmpl_hh_wrapper a,\n.tmpl_hh_wrapper img,\n.tmpl_hh_wrapper ol,\n.tmpl_hh_wrapper ul,\n.tmpl_hh_wrapper li {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    font-size: 100%;\n    font: inherit;\n    vertical-align: baseline;\n}\n\n.hht-vacancydescription {\n    padding: 0px;\n}\n\n.tmpl_hh_wrapper .l-cell,\n.tmpl_hh_wrapper .l-paddings {\n    padding: 0px !important;\n}\n\n.tmpl_hh_wrapper .b-vacancy-desc-wrapper {\n    margin-top: 0px !important;    \n}\n\n.tmpl_hh_wrapper .b-vacancy-desc {\n    overflow: visible !important;\n    line-height: inherit;\n}\n\n.tmpl_hh_content ol li b,\n.tmpl_hh_content ol li strong,\n.tmpl_hh_content ol li p b,\n.tmpl_hh_content ol li p strong,\n.tmpl_hh_content ul li b,\n.tmpl_hh_content ul li strong,\n.tmpl_hh_content ul li p b,\n.tmpl_hh_content ul li p strong {\n    font-weight: normal;\n    font-size: inherit !important;\n    color: inherit !important;\n    margin: 0 !important;\n    text-transform: none;\n    line-height: inherit;\n}\n\n.tmpl_hh_content ol li p,\n.tmpl_hh_content ul li p {\n    font-weight: normal;\n    margin: 0;\n}\n\n.tmpl_hh_content p b,\n.tmpl_hh_content p strong {\n    margin: 0 0 0;\n    padding: 28px 0 8px;\n}\n\n.tmpl_hh_wrapper {\n    width: 100%;\n    margin: 0 auto;\n    max-width: 690px;\n    position: relative;\n    word-break: normal;\n    color: #333333;\n    overflow: hidden;\n    font-family: \'Verdana\', sans-serif;\n    font-size: 14px;\n    line-height: 20px;\n    -webkit-font-smoothing: antialiased;\n}\n\n.tmpl_hh_wrapper img {\n    width: 100%;\n    display: block;\n}\n\n.tmpl_hh_content {\n    padding: 41px 40px 34px;\n    position: relative;\n    z-index: 1;\n}\n\n.tmpl_hh_content p:first-child,\n.tmpl_hh_content>strong:first-child,\n.tmpl_hh_content div>strong:first-child {\n    margin-top: 0 !important;\n}\n\n.tmpl_hh_content p,\n.tmpl_hh_content b,\n.tmpl_hh_content strong {\n   margin: 12px 0 12px;\n}\n\n.tmpl_hh_content b, \n.tmpl_hh_content strong {\n    display: inline-block;\n    font-weight: bold;\n    font-size: 24px;\n    line-height: 32px;\n}\n\n.tmpl_hh_content ol,\n.tmpl_hh_content ul {\n    margin-left: 15px;\n    list-style: none !important;\n}\n\n.tmpl_hh_content ol {\n    counter-reset: list_counter;\n}\n\n.tmpl_hh_content li {\n    position: relative;\n    margin-bottom: 12px;\n}\n\n.tmpl_hh_content ul ul,\n.tmpl_hh_content ul ol,\n.tmpl_hh_content ol ol,\n.tmpl_hh_content ol ul {\n    margin-top: 12px;\n}\n\n.tmpl_hh_content ol>li {\n    counter-increment: list_counter;    \n}\n\n.tmpl_hh_content li:before {\n    position: absolute;\n    left: -15px;\n    top: 0;\n}\n\n.tmpl_hh_content ul>li:before {\n    content: "";\n    height: 5px;\n    width: 5px;\n    background: #333333;\n    opacity: 0.45;\n    border-radius: 50%;\n    top: 7px;\n}\n\n.tmpl_hh_content ol>li:before {\n    content: counter(list_counter)\'.\';\n    left: auto;\n    right: 100%;\n    margin-right: 2px;\n}\n\n.tmpl_hh_header {\n    padding: 6.5% 40px 0;\n    background: url(https://hhcdn.ru/ichameleon/152526.png) 0 0 no-repeat;\n    background-size: 21% auto;\n}\n\n.tmpl_hh_header_logo {\n    position: relative;\n    width: 28.6%;\n}\n\n.tmpl_hh_footer {\n    position: relative;\n    background: url(https://hhcdn.ru/ichameleon/152638.jpg);\n    height: 0;\n    padding-bottom: 56.4%;\n    background-size: 100%;\n    background-repeat: no-repeat;\n}\n\np.tmpl_hh_foonote {\n    position: absolute;\n    bottom: 10px;\n    left: 4.6%;\n    font-size: 9px;\n    line-height: 9px;\n    z-index: 1;\n}\n\n@media (max-width: 699px) {\n    .tmpl_hh_content p b, .tmpl_hh_content p strong {\n        padding: 18px 0 3px;\n    }\n\n    .tmpl_hh_content b, .tmpl_hh_content strong {\n        font-size: 20px;\n        line-height: 28px;\n    }\n\n    .tmpl_hh_header {\n        padding: 12.9% 0 0% 0%;\n        background: url(https://hhcdn.ru/ichameleon/152842.png) 0 0 no-repeat;\n        background-size: 26% auto;\n    }\n\n    .tmpl_hh_header_logo {\n        width: 50.2%; \n    }\n\n    .tmpl_hh_content {\n        padding: 43px 0% 0px;\n    }\n\n    .tmpl_hh_footer {\n        background: url(https://hhcdn.ru/ichameleon/152640.jpg);\n        padding-bottom: 129.4%;\n        background-size: 106%;\n        background-repeat: no-repeat;\n        margin-top: -4%;\n    }\n\n    p.tmpl_hh_foonote {\n        position: relative;\n        top: 0;\n        left: 0;\n        margin-top: 2%;\n    }\n}\n</style>\n\n\n<div class="tmpl_hh_wrapper">\n        <div class="tmpl_hh_header">\n            <div class="tmpl_hh_header_logo"><img src="https://hhcdn.ru/ichameleon/152525.svg" alt=""></div>\n        </div>\n        <div class="tmpl_hh_content">\n            <p>Ищем специалиста по Data Science, чтобы строить и улучшать модели машинного обучения, которые помогут усовершенствовать наш продукт и внутренние процессы. Мы стремимся к тому, чтобы мы стали быстрее и удобнее для клиентов. И в этом нам нужна твоя помощь.</p> <p><strong>Что нужно делать</strong></p> <p>Придумывать и проверять гипотезы, которые позволят построить новые скоринговые модели и улучшить текущие. В результате они должны определять благонадёжность компаний по данным о бизнесе.</p> <p>Ты будешь доставать данные из баз и других источников, приводить их к нужному виду, анализировать и затем строить модели. Тебе предстоит планировать и ставить эксперименты, анализировать результаты и их статистическую значимость. Использовать feature engineering, model selection и fine tuning.</p> <p><strong>Ты подойдешь, если</strong></p> <ul> <li> <p>Знаешь Python, умеешь обрабатывать данные с помощью pandas, NumPy, Plotly.</p> </li> <li> <p>У тебя есть опыт работы с SQL.</p> </li> <li> <p>Есть опыт работы с методами машинного обучения и основными инструментами data science: Scikit-learn, XGBoost, CatBoost и т.п.</p> </li> <li> <p>Разбираешься в математической статистике, теории вероятностей и знаешь, как проводить A/B тестирования.</p> </li> </ul> <p><strong>Будет плюсом, если</strong></p> <ul> <li> <p>Ты понимаешь принципы работы нейросетей и уже имеешь опыт с одним или несколькими фреймворками для глубокого обучения: TensorFlow или PyTorch.</p> </li> <li> <p>Умеешь вводить модели машинного обучения в работу production.</p> </li> </ul> <p><strong>Что будет твоим в Точке</strong></p> <ul> <li> <p>Зарплата от 100 000 ₽. Расскажи на собеседовании, какая зарплата будет комфортной для тебя. А мы определим твой грейд по хард и софт скиллам и назовём точную сумму.</p> </li> <li> <p>Официальное трудоустройство и бесплатная страховка здоровья даже за границей.</p> </li> <li> <p>Удобный график: гибкое начало и окончание рабочего дня.</p> </li> <li> <p>Современный и уютный офис со спортзалом и библиотекой, кофе-поинтами, кухнями и зонами отдыха.</p> </li> <li> <p>Гибкие методы управления: мы используем Agile и SCRUM, чтобы каждый день создавать новое и быстро реагировать на изменения.</p> </li> <li> <p>Бесплатное корпоративное обучение: мы ездим на IT-конференции, собираемся на митапы, раздаем призы на хакатонах и проходим курсы за счёт компании.</p> </li> <li> <p>Возможность принимать решения без тысячи согласований и предлагать идеи: от простых до самых амбициозных.</p> </li> </ul> <p>И всё это в комфортных условиях без бюрократии, дресс-кода и начальников.</p></div>\n        <div class="tmpl_hh_footer"></div>\n        <p class="tmpl_hh_foonote">АО\xa0«Точка»</p>\n    </div>\n',
 'vacancy_constructor_template': None,
 'key_skills': [{'name': 'аналитика'},
  {'name': 'SQL'},
  {'name': 'Python'},
  {'name': 'Информационные технологии'},
  {'name': 'Работа с базами данных'},
  {'name': 'Математический анализ'},
  {'name': 'NumPy'},
  {'name': 'Математическая статистика'},
  {'name': 'XGBoost'}],
 'accept_handicapped': False,
 'accept_kids': False,
 'archived': False,
 'response_url': None,
 'specializations': [{'id': '17.751',
   'name': 'Другое',
   'profarea_id': '17',
   'profarea_name': 'Продажи'},
  {'id': '1.25',
   'name': 'Аналитик',
   'profarea_id': '1',
   'profarea_name': 'Информационные технологии, интернет, телеком'},
  {'id': '3.26',
   'name': 'Аналитик',
   'profarea_id': '3',
   'profarea_name': 'Маркетинг, реклама, PR'},
  {'id': '12.746',
   'name': 'Другое',
   'profarea_id': '12',
   'profarea_name': 'Консультирование'}],
 'professional_roles': [{'id': '10', 'name': 'Аналитик'}],
 'code': None,
 'hidden': False,
 'quick_responses_allowed': False,
 'driver_license_types': [],
 'accept_incomplete_resumes': False,
 'employer': {'id': '2324020',
  'name': 'Точка',
  'url': 'https://api.hh.ru/employers/2324020',
  'alternate_url': 'https://hh.ru/employer/2324020',
  'logo_urls': {'240': 'https://hhcdn.ru/employer-logo/3414734.jpeg',
   '90': 'https://hhcdn.ru/employer-logo/3414733.jpeg',
   'original': 'https://hhcdn.ru/employer-logo-original/743443.jpg'},
  'vacancies_url': 'https://api.hh.ru/vacancies?employer_id=2324020',
  'trusted': True},
 'published_at': '2022-03-11T12:37:57+0300',
 'created_at': '2022-03-11T12:37:57+0300',
 'negotiations_url': None,
 'suitable_resumes_url': None,
 'apply_alternate_url': 'https://hh.ru/applicant/vacancy_response?vacancyId=49423067',
 'has_test': False,
 'test': None,
 'alternate_url': 'https://hh.ru/vacancy/49423067',
 'working_days': [],
 'working_time_intervals': [],
 'working_time_modes': [],
 'accept_temporary': False}

Легкая часть¶

In [23]:
import sys
!{sys.executable} -m pip install openpyxl
Collecting openpyxl
  Using cached openpyxl-3.0.9-py2.py3-none-any.whl (242 kB)
Collecting et-xmlfile
  Using cached et_xmlfile-1.1.0-py3-none-any.whl (4.7 kB)
Installing collected packages: et-xmlfile, openpyxl
Successfully installed et-xmlfile-1.1.0 openpyxl-3.0.9
WARNING: You are using pip version 21.3.1; however, version 22.0.4 is available.
You should consider upgrading via the '/opt/homebrew/Cellar/jupyterlab/3.2.9/libexec/bin/python3.9 -m pip install --upgrade pip' command.

Задача 1.¶

Исследуем профессию Data Scientist. Найдите как можно больше вакансий по этой профессии в Москве. Учтите, что имеет смысл искать также по другим ключевым словам, например аналитик данных.

In [3]:
URL = 'https://api.hh.ru/vacancies'

params_array = [{
                    'text': "Data Scientist",
                    'area': 1,
                    'professional_role': 10,
                    'per_page': 100
                },
                {
                    'text': "Аналитик данных",
                    'area': 1,
                    'professional_role': 10,
                    'per_page': 100
                },
                {
                    'text': "Дата инженер",
                    'area': 1,
                    'professional_role': 10,
                    'per_page': 100
                },
                {
                    'text': "Data analyst",
                    'area': 1,
                    'professional_role': 10,
                    'per_page': 100
                },
                {
                    'text': "Big data",
                    'area': 1,
                    'professional_role': 10,
                    'per_page': 100
                }
]

jobs = []

for params in params_array:
    params['page'] = 0
    pages = json.loads(requests.get(URL, params).content.decode())['pages']
    for i in range(pages):
        params['page'] = i
        req = requests.get(URL, params)
        data = json.loads(req.content.decode())
        jobs += data['items']
        # print(*[value['name'] for value in data['items']], sep='\n') # Исследование ненужных профессий

print(f"Аналитики, нефильтрованные, с повторами: {len(jobs)}")

prohibited = ['бизнес', 'системный', 'веб', 'web', 'продукт', 'маркетолог',
              'продаж', 'marketing', 'маркетинг', 'business', 'финансовый',
              'product', 'sales', 'system', 'digital', 'market']
filtered = []
for job in jobs:
    OK = True
    for unit in prohibited:
        if (unit in job['name'].lower()):
            OK = False
    if (OK):
        filtered += [job]
jobs = filtered

print(f"Аналитики, фильтрованные, с повторами: {len(jobs)}")
Аналитики, нефильтрованные, с повторами: 3135
Аналитики, фильтрованные, с повторами: 1781

В полученную выборку некоторые вакансии могли попасть несколько раз. Удалите дубликаты.

In [4]:
jobs_df = pd.json_normalize(jobs)
jobs_df.drop_duplicates(subset='id', inplace=True, ignore_index = True)
print(f"Аналитики, фильтрованные, без повторов: {len(jobs_df)}")
Аналитики, фильтрованные, без повторов: 1328
In [ ]:
print(*list(jobs_df['name']), sep='\n') # Больше негативных паттернов не нашлось, данные стали чище

Загрузите подробное описание каждой вакансии и создайте удобную таблицу данных.

In [ ]:
descriptions = []
for i in range(len(jobs_df)):
    vacancy = jobs_df['id'].iloc[i]
    vacancy_url = f'https://api.hh.ru/vacancies/{vacancy}'
    
    req = requests.get(vacancy_url)
    response = json.loads(req.content.decode())
    try:
        descriptions += [response]
        check = response['description']
        print(f"Status: {i}, description: {check[:50]}")
    except Exception:
        print(f"Failed response: {response}")
        raise
        
final = jobs_df.merge(pd.json_normalize(descriptions), left_on='id', right_on='id', suffixes=[None, '_remove'])
final.drop([column for column in final.columns if '_remove' in column], axis=1, inplace=True)

Проверим, что остались не бесполезные поля:

In [20]:
columns = final.isna().sum()
to_drop = columns[columns == len(final)]
print(to_drop)
jobs = final.drop(columns=to_drop.keys())
department                                     1328
address                                        1328
sort_point_distance                            1328
insider_interview                              1328
contacts                                       1328
salary                                         1328
address.description                            1328
address.metro                                  1328
employer.logo_urls                             1328
vacancy_constructor_template                   1328
negotiations_url                               1328
suitable_resumes_url                           1328
test                                           1328
vacancy_constructor_template.bottom_picture    1328
dtype: int64

Полученную таблицу необходимо сохранить в формате xlsx и отправить боту вместе с решением.

In [ ]:
import openpyxl
jobs_to_save = jobs.drop(columns='branded_description') # Важно, чтобы в excel было читабельно
jobs_to_save.to_excel("jobs.xlsx")

Вопрос 1. Сколько сейчас доступно вакансий по вашему запросу?

In [4]:
jobs = pd.read_excel('jobs.xlsx')
print(f"Доступно вакансий: {len(jobs)}")
Доступно вакансий: 1328

Вопрос 2. Какие навыки чаще всего встречаются в вакансиях по данной специальности?

Для этого найдите соответствующее поле в данных, проанализируйте его и составьте список навыков и количество упоминаний каждого. Визуализируйте полученную информацию по топ-15 навыков.

In [6]:
import ast
SHOW_SIZE = 15
skills_freq = dict()
skills_arr = list(jobs['key_skills'])
for skills in skills_arr:
    skills_json = ast.literal_eval(skills)
    for skill in skills_json:
        skill_name = skill['name']
        if (skill_name not in skills_freq):
            skills_freq[skill_name] = 0
        skills_freq[skill_name] += 1
items = sorted(skills_freq.items(), key=lambda item: item[1], reverse=True)
values = [x[1] for x in items]
labels = [x[0] for x in items]
fig = plt.figure(figsize=(12, 12))
ax = plt.axes()
size = sum(values[:SHOW_SIZE])
actual_size = len(skills_arr)
ax.barh(np.arange(SHOW_SIZE), values[:SHOW_SIZE][::-1]);
ax.set_yticks(np.arange(SHOW_SIZE), labels=labels[:SHOW_SIZE][::-1])
ax.set_title("Топ востребованных навыков в Data science");
ax.set_xlabel("Количество вакансий");

Вопрос 3. Какую зарплату готовы платить работодатели? Соберите некоторым образом статистику и постройте гистограмму.

При работе с данными о заработной плате обратите внимание на валюту и gross/net.

In [7]:
jobs['salary.currency'].drop_duplicates()
Out[7]:
0     RUR
1     NaN
14    EUR
20    USD
Name: salary.currency, dtype: object
In [8]:
def get_salary(obj):
    ratio = {'RUR': 1, 'EUR': 131, 'USD': 120}
    tax_ratio = 0.87
    return obj['salary.from'] * ratio[obj['salary.currency']] * (tax_ratio if obj['salary.gross'] else 1)

salaries = list(jobs.reindex(columns=['salary.currency', 'salary.from', 'salary.gross']).dropna().apply(get_salary, axis=1))
fig = plt.figure(figsize=(12, 12))
ax = plt.axes()
ax.hist(salaries, bins=40)
ax.set_xlim((0, 350000));
ax.set_ylabel('Количество вакансий')
ax.set_xlabel('Заработная плата (RUR), "чистыми"')
ax.set_title('Распределение ЗП среди аналитиков данных');

Выводы: SQL, Python и аналитическое мышление являются ключевыми навыками. Заметны просадки в зарплате в районе 90-99 тыс. и 190-199 тыс. Это можно объяснить попыткой работодателей увеличить "порядок" зарплаты. Также было полезно проанализировать поля датасета и получить более точные данные по зарплате: удалось перевести все цифры в net зарплату в рублях. Также было полезно сохранять датасеты, чтобы восстанавливаться из них, а не парсить вновь.

Простая часть 2 - Анализ вакансий Software Python Developer в Санкт-Петербурге¶

Проведите аналогичный анализ для наиболее привлекательной для вас профессии в любом регионе. Если это Data Scientist, то для анализа выберите другую.

In [41]:
URL = 'https://api.hh.ru/vacancies'

params_array = [{
                    'text': "Python Developer",
                    'area': 2,
                    'professional_role': 96,
                    'per_page': 100
                },
                {
                    'text': "Разработчик Python",
                    'area': 2,
                    'professional_role': 96,
                    'per_page': 100
                },
                {
                    'text': "Backend Developer Python",
                    'area': 2,
                    'professional_role': 96,
                    'per_page': 100
                },
                {
                    'text': "Software Developer Python",
                    'area': 2,
                    'professional_role': 96,
                    'per_page': 100
                }
]

jobs = []

for params in params_array:
    params['page'] = 0
    pages = json.loads(requests.get(URL, params).content.decode())['pages']
    for i in range(pages):
        params['page'] = i
        req = requests.get(URL, params)
        data = json.loads(req.content.decode())
        jobs += data['items']
        # print(*[value['name'] for value in data['items']], sep='\n') # Исследование ненужных профессий

print(f"Разработчики, нефильтрованные, с повторами: {len(jobs)}")

prohibited = ['java', 'devops', 'frontend', 'c++', 'js',
             'c#', 'scientist', '1с', 'web', 'с++', 'go',
              'ruby', '.net']
filtered = []
for job in jobs:
    OK = True
    for unit in prohibited:
        if (unit in job['name'].lower()):
            OK = False
    if (OK):
        filtered += [job]
jobs = filtered

print(f"Разработчики, фильтрованные, с повторами: {len(jobs)}")

jobs_df = pd.json_normalize(jobs)
jobs_df.drop_duplicates(subset='id', inplace=True, ignore_index = True)
print(f"Разработчики, фильтрованные, без повторов: {len(jobs_df)}")
Разработчики, нефильтрованные, с повторами: 1832
Разработчики, фильтрованные, с повторами: 1190
Разработчики, фильтрованные, без повторов: 526

Проверим, кто остался:

In [ ]:
print(*list(jobs_df['name']), sep='\n') # Больше негативных паттернов не нашлось, данные стали чище

Загрузим вакансии:

In [ ]:
descriptions = []
for i in range(len(jobs_df)):
    vacancy = jobs_df['id'].iloc[i]
    vacancy_url = f'https://api.hh.ru/vacancies/{vacancy}'
    
    req = requests.get(vacancy_url)
    response = json.loads(req.content.decode())
    try:
        descriptions += [response]
        check = response['description']
        print(f"Status: {i}, description: {check[:50]}")
    except Exception:
        print(f"Failed response: {response}")
        raise
        
final = jobs_df.merge(pd.json_normalize(descriptions), left_on='id', right_on='id', suffixes=[None, '_remove'])
final.drop([column for column in final.columns if '_remove' in column], axis=1, inplace=True)

Проверим, что остались не бесполезные поля:

In [44]:
columns = final.isna().sum()
to_drop = columns[columns == len(final)]
print(to_drop)
jobs = final.drop(columns=to_drop.keys())
department                      526
response_url                    526
sort_point_distance             526
insider_interview               526
contacts                        526
address.description             526
address.metro                   526
salary                          526
address                         526
employer.logo_urls              526
vacancy_constructor_template    526
negotiations_url                526
suitable_resumes_url            526
test                            526
dtype: int64

Вопрос 1. Сколько сейчас доступно вакансий по вашему запросу?

In [11]:
jobs = pd.read_excel('jobs_python.xlsx')
print(f"Доступно вакансий: {len(jobs)}")
Доступно вакансий: 526

Вопрос 2. Какие навыки чаще всего встречаются в вакансиях по данной специальности?

Для этого найдите соответствующее поле в данных, проанализируйте его и составьте список навыков и количество упоминаний каждого. Визуализируйте полученную информацию по топ-15 навыков.

In [13]:
import ast
SHOW_SIZE = 15
skills_freq = dict()
skills_arr = list(jobs['key_skills'])
for skills in skills_arr:
    skills_json = ast.literal_eval(skills)
    for skill in skills_json:
        skill_name = skill['name']
        if (skill_name not in skills_freq):
            skills_freq[skill_name] = 0
        skills_freq[skill_name] += 1
items = sorted(skills_freq.items(), key=lambda item: item[1], reverse=True)
values = [x[1] for x in items]
labels = [x[0] for x in items]
fig = plt.figure(figsize=(12, 12))
ax = plt.axes()
size = sum(values[:SHOW_SIZE])
actual_size = len(skills_arr)
ax.barh(np.arange(SHOW_SIZE), values[:SHOW_SIZE][::-1]);
ax.set_yticks(np.arange(SHOW_SIZE), labels=labels[:SHOW_SIZE][::-1])
ax.set_title("Топ востребованных навыков в Data science");
ax.set_xlabel("Количество вакансий");

Вопрос 3. Какую зарплату готовы платить работодатели? Соберите некоторым образом статистику и постройте гистограмму.

При работе с данными о заработной плате обратите внимание на валюту и gross/net.

In [14]:
jobs['salary.currency'].drop_duplicates()
Out[14]:
0     RUR
2     NaN
5     USD
10    EUR
Name: salary.currency, dtype: object
In [16]:
def get_salary(obj):
    ratio = {'RUR': 1, 'EUR': 131, 'USD': 120}
    tax_ratio = 0.87
    return obj['salary.from'] * ratio[obj['salary.currency']] * (tax_ratio if obj['salary.gross'] else 1)

salaries = list(jobs.reindex(columns=['salary.currency', 'salary.from', 'salary.gross']).dropna().apply(get_salary, axis=1))
fig = plt.figure(figsize=(12, 12))
ax = plt.axes()
ax.hist(salaries, bins=50)
ax.set_xlim((0, 350000));
ax.set_ylabel('Количество вакансий')
ax.set_xlabel('Заработная плата (RUR), "чистыми"')
ax.set_title('Распределение ЗП среди python разработчиков');
In [49]:
import openpyxl
jobs_to_save = jobs.drop(columns='branded_description') # Важно, чтобы в excel было читабельно
jobs_to_save.to_excel("jobs_python.xlsx")

Выводы: Ожидаемо владение Python, Linux, Git и SQL являются ключевыми навыками. Заметны просадки в зарплате в районе 90-99 тыс., 140-149 тыс., 190-199 тыс., 240-249 тыс. Это можно объяснить попыткой работодателей увеличить "порядок" зарплаты. Зарплаты разработчиков в Санкт-Петербурге значительно больше, чем аналитиков в Москве. Это можно объяснить тем, что в первую выборку попало достаточно вакансий аналитиков без владения инструментами Data Science.


Сложная часть¶

Задача 2.¶

Для одной из рассмотренных ранее профессий ответьте на следующие вопросы:

  • Какой опыт требуется для данной вакансии?
  • Какие работодатели в топе?
  • В каком районе Москвы данная вакансия пользуется наибольшим и наименьшим спросом?

Не забудьте про визуализацию, для этого используйте библиотеку plotly. В частности, постройте распределений вакансий на карте.

Для построения последних двух графиков вам могут понадобиться внешние данные.

Рассмотрим аналитиков.

In [6]:
import sys
!{sys.executable} -m pip install plotly==5.6.0
import plotly
import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as pyo
jobs = pd.read_excel('jobs.xlsx') 
Requirement already satisfied: plotly==5.6.0 in /opt/homebrew/Cellar/jupyterlab/3.2.9/libexec/lib/python3.9/site-packages (5.6.0)
Requirement already satisfied: tenacity>=6.2.0 in /opt/homebrew/Cellar/jupyterlab/3.2.9/libexec/lib/python3.9/site-packages (from plotly==5.6.0) (8.0.1)
Requirement already satisfied: six in /opt/homebrew/Cellar/six/1.16.0_2/lib/python3.9/site-packages (from plotly==5.6.0) (1.16.0)
WARNING: You are using pip version 21.3.1; however, version 22.0.4 is available.
You should consider upgrading via the '/opt/homebrew/Cellar/jupyterlab/3.2.9/libexec/bin/python3.9 -m pip install --upgrade pip' command.
Задание 1. Опыт¶
In [7]:
print("Возможные значения: ", list(jobs['experience.name'].drop_duplicates().values))
print("Количество пропусков: ", jobs['experience.name'].isna().sum())
Возможные значения:  ['От 3 до 6 лет', 'От 1 года до 3 лет', 'Нет опыта', 'Более 6 лет']
Количество пропусков:  0
In [8]:
pyo.init_notebook_mode()
exp = jobs.rename(columns={'experience.name': 'Опыт работы'})
exp['Количество'] = 1
fig = px.pie(exp, values='Количество', names='Опыт работы', title="Необходимый опыт работы для аналитика")
pyo.iplot(fig, filename = 'basic-line')
Задание 2. Топ работодателей¶
In [9]:
employers = jobs.groupby(by='employer.name').count()['id'].sort_values(ascending=False)
values = list(employers[:10].values)[::-1]
labels = list(employers[:10].keys())[::-1]

fig = go.Figure(go.Bar(x=values, y=labels, orientation='h'))
fig.update_layout(title_text='Крупнейшие работодатели для аналитиков',
                 xaxis_title='Количество вакансий')
pyo.iplot(fig, filename = 'basic-line')
Задание 3. Вакансии на карте¶
In [58]:
import plotly.express as px
px.set_mapbox_access_token('pk.eyJ1Ijoib3JhbmdlZ29kIiwiYSI6ImNsMHNpcDd0cjA0cXozYm4xb21uZjZ3czkifQ.98Jxh6dRK8VaxKkURGTQeA')

metro_stations = jobs.groupby(by=['address.metro.lat', 'address.metro.lng', 'address.metro.station_name']).count()['id']
metro_stations = metro_stations.reset_index().rename(columns={"id": "Вакансий"})
fig = px.scatter_mapbox(metro_stations, lat="address.metro.lat", lon="address.metro.lng",
                        text="address.metro.station_name", size="Вакансий", zoom=10.5,
                        hover_name="address.metro.station_name",
                        hover_data={"address.metro.lat": False,
                                    "address.metro.lng": False,
                                    "address.metro.station_name": False},
                        center=dict(lat=55.74, lon=37.62))

fig.update_layout(title_text='Вакансии для аналитиков')
pyo.iplot(fig, filename = 'basic-line')

Какие выводы можно сделать из построенных графиков?

Выводы: зачастую для аналитика в Москве достаточно минимального опыта работы (1-3 года). Крупнейший работодатель в этой сфере - Сбербанк. Наибольшим спросом вакансия аналитика пользуется в районах станций метро: Выставочная, Деловой центр, Технопарк и Бауманская, а также вдоль Ленинградского проспекта: станции Динамо, Белорусская, Аэропорт. Наименьшим спросом - на окраинах: станции Соколинная Гора, Бабушкинакая, Покровское, а также в центре Москвы: станции Китай-город, Смоленская, Таганская, Полянка.